/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2021. All rights reserved.
 * Description: hkrr help functions
 * Author: wangzhu
 * Create: 2021-07-05
 * Notes: it includes print functions and hkrr seed functions.
 */

#include "hkrr_utils.h"
#include "../arch/arm/boot/compressed/lcgrand.c"

#ifdef CONFIG_RTOS_HKRR_DEBUG
#define TMP_SIZE 24
#define LOG_BUF_LEN (1 << 20)
static __initdata char hkrr_log_buf[LOG_BUF_LEN] __aligned(8);
static __initdata int hkrr_log_pos;
#endif

#if defined(CONFIG_ARM64)
static inline u64 hkrr_get_cntvct(void)
{
	u64 cval;

	isb();
	asm volatile("mrs %0, cntvct_el0" : "=r" (cval));
	return cval;
}
#else
static inline u64 hkrr_get_cntvct(void)
{
	u64 cval;

	isb();
	asm volatile("mrrc p15, 1, %Q0, %R0, c14" : "=r" (cval));
	return cval;
}
#endif

#ifdef CONFIG_CORTEX_A9
void __init setup_processor(void);
#endif

static uint64_t __init get_seed_from_timer(void *fdt)
{
#ifdef CONFIG_CORTEX_A9
#define SOURCE_LISTS_SIZE	3
	struct random_source timer_sources[SOURCE_LISTS_SIZE] = {
		/* twd timer counter offset is 0x4 */
		{ "arm,cortex-a9-twd-timer", { 0x4 }, 1 },
		/* global timer(64 bit) count offset is 0x0 and 0x4 */
		{ "arm,cortex-a9-global-timer", { 0x0, 0x4 }, 2 },
		/* sp804 timer1_val offset is 0x4 */
		{ "arm,sp804", { 0x4 }, 1  },
	};
	uint64_t seed = 0;
	const fdt32_t *prop_addr = NULL;
	const volatile void *addr = NULL;
	int timer, len, i, j;

	setup_processor();
	early_fixmap_init();
	for (i = 0; i < SOURCE_LISTS_SIZE; i++) {
		timer = fdt_node_offset_by_compatible(fdt, -1, timer_sources[i].dts_compatible);
		if (timer < 0)
			continue;
		prop_addr = fdt_getprop(fdt, timer, "reg", &len);
		if (!prop_addr)
			continue;
		addr = (const volatile void *)fdt32_to_cpu(*prop_addr);
		addr = (void __iomem *)set_fixmap_offset_io(FIX_EARLYCON_MEM_BASE, (uint32_t)addr);
		for (j = 0; j < timer_sources[i].offs_cnt; j++)
			seed += readl(addr + timer_sources[i].reg_conter_offs[j]);
		clear_fixmap(FIX_EARLYCON_MEM_BASE);
	}
#undef SOURCE_LISTS_SIZE
	return seed;
#else
	return hkrr_get_cntvct();
#endif
}

uint64_t __init hkrr_get_hkrr_seed(void)
{
#define MAX_CYCLES 3
	void *fdt = NULL;
	uint64_t seed = 0;
	uint64_t tmp_seed = 0;
	int cycles = 0;
	int node, len;
	uint64_t *prop = NULL;
	const uint8_t *cmdline = CONFIG_CMDLINE;
	char *str = NULL;

#ifdef CONFIG_ARM64
	int size;
	early_fixmap_init();
	fdt = fixmap_remap_fdt(__fdt_pointer, &size, PAGE_KERNEL);
	if (!fdt)
		return 0;
#else
	fdt = FDT_VIRT_BASE(__atags_pointer);
	if (!fdt)
		return 0;
#endif
	node = fdt_path_offset(fdt, "/chosen");
	if (node < 0)
		return 0;

	if (!IS_ENABLED(CONFIG_CMDLINE_FORCE)) {
		const uint8_t *prop = fdt_getprop(fdt, node, "bootargs", NULL);
		if (prop)
			cmdline = prop;
	}

	if (strstr(cmdline, "nohkrr"))
		return 0;

	prop = fdt_getprop_w(fdt, node, "hkrr-seed", &len);
	if (!prop || len != sizeof(uint64_t)) {
		str = strstr(cmdline, "hkrr-seed=");
		if (str) {
			sscanf(str + strlen("hkrr-seed="), "%llx", &seed);
			str = str + strlen("hkrr-seed=");
			for (; *str != '\0' && *str != ' '; str++)
				*str = '*';
			return seed;
		}

		/*
		 * The code segment is not random when the lower 16 bits of the seed are 0
		 * Regenerate random seed several times to prevent lower 16 bits from being 0
		 */
		do {
			tmp_seed += get_seed_from_timer(fdt);
			seed = random_get((u32)tmp_seed);
			cycles += 1;
		} while ((seed & 0xffff) == 0 && cycles <= MAX_CYCLES);

		return seed;
	}

	seed = fdt64_to_cpu(*prop);
	/* Set prop to 0 to prevent seed exposure */
	*prop = 0;

	return seed;
}

#ifdef CONFIG_RTOS_HKRR_DEBUG
/*
 * print string, parameters:
 * 1. buf: print buffer
 * 2. str: target string to print
 * return value:
 * number of characters printed
 */
static int __init hkrr_print_str(char *buf, const char *str, char *buf_end)
{
	int len;

	if (buf == NULL || str == NULL || buf_end == NULL)
		return 0;

	len = strlen(str);
	if (buf >= buf_end || len >= buf_end - buf - 1)
		return 0;

	strcpy(buf, str);

#ifdef CONFIG_DEBUG_LL
	printascii(str);
#endif
	return len;
}

/*
 * transfer number to string, paramters:
 * 1. buf: transfer buffer
 * 2. num: target number
 * 3. base: transfer base
 * return value:
 * 0: successful
 * -1: failed
 */
static int __init hkrr_num2str(char *buf, int buf_len, unsigned long num, int base)
{
	int i;
	unsigned long n;
	int len = 0;

	if (buf == NULL || buf_len <= 0 || base < HKRR_MIN_BASE || base > HKRR_MAX_BASE)
		return -1;

	n = num;
	while (n != 0) {
		++len;
		n /= base;
	}

	if (len == 0)
		++len;

	if (len > buf_len)
		return -1;

	for (i = len - 1; i >= 0; i--) {
		n = num % base;
		if (n < 10)
			buf[i] = n + '0';
		else
			buf[i] = n - 10 + 'a';
		num /= base;
	}
	buf[len] = '\0';

	return 0;
}

/*
 * print string in hkrr log and uart
 */
void __init hkrr_log_str(const char *str)
{
	int pnum;

	pnum = hkrr_print_str(hkrr_log_buf + hkrr_log_pos, str,
				hkrr_log_buf + LOG_BUF_LEN);
	if (pnum != 0)
		hkrr_log_pos = hkrr_log_pos + pnum + 1;
}

/*
 * print string and number
 * dec: 0, hexical number, 1, decimal number
 */
void __init hkrr_log_strnum(const char *str, unsigned long num, bool dec)
{
	char tmp[TMP_SIZE];
	int num1, num2;
	int base;

	base = (dec == true) ? 10 : 16;
	if (hkrr_num2str(tmp, TMP_SIZE, num, base))
		return;

	num1 = hkrr_print_str(hkrr_log_buf + hkrr_log_pos, str,
				hkrr_log_buf + LOG_BUF_LEN);
	num2 = hkrr_print_str(hkrr_log_buf + hkrr_log_pos + num1, tmp,
				hkrr_log_buf + LOG_BUF_LEN);

	if (num1 + num2 != 0)
		hkrr_log_pos = hkrr_log_pos + num1 + num2 + 1;
#ifdef CONFIG_DEBUG_LL
	printascii("\n");
#endif
}

void __init hkrr_print_log(void)
{
	char *buf = NULL;
	char *end = NULL;
	const char *msg = NULL;

	pr_info("LOG POS: %d\n", hkrr_log_pos);
	if (!hkrr_log_pos)
		return;

	buf = hkrr_log_buf;
	end = hkrr_log_buf + hkrr_log_pos;
	for (; buf < end;) {
		msg = buf;
		pr_info("%s", msg);
		buf += 1 + strlen(msg);
	}
}

void __init hkrr_print_addrs(void)
{
#ifdef CONFIG_ARM64
	uint64_t *p =  (uint64_t *)_text;

	hkrr_log_strnum("kernel_offset: 0x", le64_to_cpu(p[1]), 0);
	hkrr_log_strnum("kernel_size: 0x", le64_to_cpu(p[2]), 0);
	hkrr_log_strnum("kernel_flags: 0x", le64_to_cpu(p[3]), 0);
	hkrr_log_strnum("FDT: 0x", __fdt_pointer, 0);
	hkrr_log_strnum("KIMAGE_VADDR: 0x", KIMAGE_VADDR, 0);
	hkrr_log_strnum("kimage_vaddr: 0x", kimage_vaddr, 0);
	hkrr_log_strnum("kimage_voffset: 0x", kimage_voffset, 0);
	hkrr_log_strnum("physical address: 0x", kimage_vaddr - kimage_voffset, 0);
	hkrr_log_strnum("KASLR offset: 0x", kaslr_offset(), 0);
	hkrr_log_strnum("SWAPPER_TABLE_SHIFT: ", SWAPPER_TABLE_SHIFT, 1);
#endif

	hkrr_log_strnum("PGDIR_SHIFT: ", PGDIR_SHIFT, 1);
	hkrr_log_strnum("PTRS_PER_PGD: ", PTRS_PER_PGD, 1);
	hkrr_log_strnum("PUD_SHIFT: ", PUD_SHIFT, 1);
	hkrr_log_strnum("PTRS_PER_PUD: ", PTRS_PER_PUD, 1);
	hkrr_log_strnum("PTRS_PER_PTE: ", PTRS_PER_PTE, 1);
	hkrr_log_strnum("swapper_pg_dir: 0x", (unsigned long)swapper_pg_dir, 0);
	hkrr_log_strnum("_text: 0x", (unsigned long)_text, 0);
	hkrr_log_strnum("_stext: 0x", (unsigned long)_stext, 0);
	hkrr_log_strnum("_etext: 0x", (unsigned long)_etext, 0);
	hkrr_log_strnum("_end: 0x", (unsigned long)_end, 0);
}

#else
void __init hkrr_log_str(const char *str) {}

void __init hkrr_log_strnum(const char *str, unsigned long num, bool dec) {}

void __init hkrr_print_log(void) {}

void __init hkrr_print_addrs(void) {}
#endif
